# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""project stuffs

Classes:
Project -- Project context

"""


import cPickle

import svn.ra

from svn.core import SWIG_SVN_INVALID_REVNUM as SVN_INVALID_REVNUM

import gvn.errors
import gvn.repository
import gvn.util


_DEFAULT_CONFIG = {
  'change-branch-base': 'changes',
  'diff-lines': 1000,
  'mail-template': """Subject: %(diff_size_desc)s (%(change_name_at_head)s)

I'd like you to do a code review.  To review this change, run

  gvn review --project %(project)s %(change_name)s

Alternatively, to review the latest snapshot of this change
branch, run

  gvn --project %(project)s review %(change_name_at_head)s

to review the following change:

%(description)s

This is a semiautomated message from "gvn mail".  See
<http://code.google.com/p/gvn/> to learn more.

%(shortdiff)s
""",

  'bug-links': [],
}


class Project(object):
  def __init__(self, username, url, config, pool=None, path=None):
    """Initialize Project.

    One of two conditions must be true:
      1. url is the project url and path is None
      2. url is the repository root and path is relative project path

    E.g.
      1. url='http://svn.apache.org/repos/asf/apr/apr', path=None
      2. url='http://svn.apache.org/repos/asf', path='apr/apr'

    See the path docstring for more.

    TODO(epg): If the caller wants to use the repository root as the
    project and wants to prevent the Project.path network hit, can he
    specify path=''?  Not sure if that does or should work...

    Arguments:
    username -- used for changebranch paths and Repository auth
    url      -- URL of either the project or the repository root
    config   -- Config for Repository
    pool     -- memory pool for Repository and Load (c.f.)
    path     -- optional path of project (default None)
    """

    self._username = username
    self._url = url
    self._config = config
    self._path = path
    self._pool = pool

    self._project_config = None
    self._repository = None

  def _GetPath(self):
    if self._path is None:
      self._path = gvn.util.RelativePath(self.repository.URL(), self._url)
    return self._path
  path = property(_GetPath,
doc="""If Project was initialized with path=None (the default),
    accessing this requires a round-trip or two to the repository (and
    therefore the network, for remote repositories), because _GetPath
    must use Repository.URL to figure out the project path, and
    Repository.URL must ask the repository for the root URL
    (round-trip #1) and reparent if the specified url is not the root
    URL (round-trip #2).
    """)

  def _GetRepository(self):
    if self._repository is None:
      self._repository = gvn.repository.Repository(self._username,
                                                   self._url,
                                                   self._config, self._pool)
      if self._path is not None:
        # If self._path is not None, it had to have been specified at
        # initialization; the only other way it can be not None is
        # *after* self._repository is initialized.  So, this means
        # condition 2 in the __init__ docstring: self._url is the URL
        # of the repository root.
        self._repository.have_root_url = True
    return self._repository
  repository = property(_GetRepository)

  def _GetProjectConfig(self, key):
    if self._project_config is None:
      self.Load()
    return self._project_config[key]
  changebranch_base = property(lambda s: s._GetProjectConfig('change-branch-base'))
  diff_lines = property(lambda s: s._GetProjectConfig('diff-lines'))
  mail_template = property(lambda s: s._GetProjectConfig('mail-template'))
  bug_links = property(lambda s: s._GetProjectConfig('bug-links'))

  def ChangeBranchPath(self, username=None, changename=None, rest=None):
    components = [self.changebranch_base]
    if username is not None:
      components.append(username)
      if changename is not None:
        components.append(changename)
        if rest is not None:
          components.append(rest)
    return '/'.join(components)

  # XXX maybe should be private
  def Load(self):
    """Load project metadata from repository.

    Use self._pool for the svn.ra.get_dir2 call.
    """

    self._project_config = dict(_DEFAULT_CONFIG)

    (dirents, revision, properties) = svn.ra.get_dir2(self.repository.ra,
                                                      self.path,
                                                      SVN_INVALID_REVNUM,
                                                      0, # dirent_fields
                                                      self._pool)
    try:
      config = cPickle.loads(properties['gvn:project'])
      try:
        # Make change-branch-base relative to repository root.
        cbb = config['change-branch-base']
        if cbb.startswith('/'):
          config['change-branch-base'] = cbb.lstrip('/')
        else:
          config['change-branch-base'] = '/'.join([self.path, cbb])
      except KeyError:
        pass

      self._project_config.update(config)
    except KeyError:
      pass
